mod admin;
mod config;
mod db;
mod entity;
mod errors;
mod mock_routes;
mod proxy;
mod layers;

use std::{str::FromStr, time::Duration};

use anyhow::Error;
use axum::{ middleware, routing::{get, post}, Extension, Router
};
use tower::ServiceBuilder;
use tower_http::{compression::CompressionLayer, trace::{self, TraceLayer}};
use tracing::{info,debug,Level};
use admin::{basic_op, memory_op};
use chrono::Local;
use clap::Parser;
use config::{app,cnf::Args};
use proxy::{index, match_route};
use tokio::time;

use crate::layers::op_auth::AuthOpChecker;

#[tokio::main]
async fn main() -> Result<(), Error> {
    let args: Args = Args::parse();
    args.check()?;
    if args.version {
        print_verion_build();
        return Ok(());
    }
    let memory_tick = args.memory_fresh_tick;
    let app_cnf = app::App::from_file(args.config_path.as_str())?;

    let subscriber = tracing_subscriber::FmtSubscriber::builder()
        .with_max_level(Level::from_str(app_cnf.log_level.clone().unwrap().as_str())?)
        .finish();
    tracing::subscriber::set_global_default(subscriber)?;

    mock_routes::init_dsn(app_cnf.database()?.to_dsn().as_str());
    tokio::spawn(async move {
        let mut ticker = time::interval(Duration::from_secs(memory_tick));
        info!("server router map start update {}s",  memory_tick);
        loop {
            ticker.tick().await;
            mock_routes::ROUTERS.lock().await.update().await.unwrap();
            debug!("[{}]routes updated interval",  Local::now().format("%Y-%m-%d %H:%M:%S"));
        }
    });

    let pool = sqlx::MySqlPool::connect(app_cnf.database()?.to_dsn().as_str()).await?;
    let listen_addr = app_cnf.http()?.ip_str();
    info!("server started at {}", listen_addr);

    let trace_layer = TraceLayer::new_for_http()
         .make_span_with(trace::DefaultMakeSpan::new().level(Level::INFO))
         .on_response(trace::DefaultOnResponse::new().level(Level::INFO));
    let app = Router::new()
        .route("/", get(index))
        .route("/", post(index))
        .route("/admin/add_route", post(basic_op::add_route).layer(middleware::from_extractor::<AuthOpChecker>()))
        .route("/admin/get_routes", get(basic_op::get_routes).layer(middleware::from_extractor::<AuthOpChecker>()))
        .route("/admin/del_route", post(basic_op::del_route).layer(middleware::from_extractor::<AuthOpChecker>()))
        .route("/admin/update_route", post(basic_op::update_route).layer(middleware::from_extractor::<AuthOpChecker>()))
        .route("/admin/fresh_route", post(memory_op::fresh_route_data).layer(middleware::from_extractor::<AuthOpChecker>()))
        .fallback(match_route)
        .layer(Extension(app_cnf))
        .layer(Extension(pool))
        .layer(
            ServiceBuilder::new()
            .layer(trace_layer)
            .layer(CompressionLayer::new())
        );
    let listener = tokio::net::TcpListener::bind(listen_addr).await?;
    axum::serve(listener, app).await?;
    Ok(())
}


fn print_verion_build() {
    println!("version: {}  compiled at: {} build sha1:{}",
        env!("CARGO_PKG_VERSION"),
        env!("COMPILED_AT"),
        env!("GIT_HEAD")
    );
}